{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Review Tool Calls\n",
    "\n",
    "!!! tip \"Prerequisites\"\n",
    "\n",
    "    This guide assumes familiarity with the following concepts:\n",
    "\n",
    "    * [Tool calling](https://js.langchain.com/docs/concepts/tool_calling/)\n",
    "    * [Human-in-the-loop](/langgraphjs/concepts/human_in_the_loop)\n",
    "    * [LangGraph Glossary](/langgraphjs/concepts/low_level)      \n",
    "\n",
    "Human-in-the-loop (HIL) interactions are crucial for [agentic systems](https://langchain-ai.github.io/langgraphjs/concepts/agentic_concepts/#human-in-the-loop). A common pattern is to add some human in the loop step after certain tool calls. These tool calls often lead to either a function call or saving of some information. Examples include:\n",
    "\n",
    "- A tool call to execute SQL, which will then be run by the tool\n",
    "- A tool call to generate a summary, which will then be saved to the State of the graph\n",
    "\n",
    "Note that using tool calls is common **whether actually calling tools or not**.\n",
    "\n",
    "There are typically a few different interactions you may want to do here:\n",
    "\n",
    "1. Approve the tool call and continue\n",
    "2. Modify the tool call manually and then continue\n",
    "3. Give natural language feedback, and then pass that back to the agent instead of continuing\n",
    "\n",
    "We can implement these in LangGraph using the [`interrupt()`](/langgraphjs/reference/functions/langgraph.interrupt-1.html) function. `interrupt` allows us to stop graph execution to collect input from a user and continue execution with collected input:\n",
    "\n",
    "```typescript\n",
    "function humanReviewNode(state: typeof GraphAnnotation.State) {\n",
    "  // this is the value we'll be providing via new Command({ resume: <human_review> })\n",
    "  const humanReview = interrupt({\n",
    "    question: \"Is this correct?\",\n",
    "    // Surface tool calls for review\n",
    "    tool_call,\n",
    "  });\n",
    "\n",
    "  const [reviewAction, reviewData] = humanReview;\n",
    "\n",
    "  // Approve the tool call and continue\n",
    "  if (reviewAction === \"continue\") {\n",
    "    return new Command({ goto: \"run_tool\" });\n",
    "  }\n",
    "  \n",
    "  // Modify the tool call manually and then continue\n",
    "  if (reviewAction === \"update\") {\n",
    "    const updatedMsg = getUpdatedMsg(reviewData);\n",
    "    return new Command({ goto: \"run_tool\", update: { messages: [updatedMsg] } });\n",
    "  }\n",
    "  \n",
    "  // Give natural language feedback, and then pass that back to the agent\n",
    "  if (reviewAction === \"feedback\") {\n",
    "    const feedbackMsg = getFeedbackMsg(reviewData);\n",
    "    return new Command({ goto: \"call_llm\", update: { messages: [feedbackMsg] } });\n",
    "  }\n",
    "  \n",
    "  throw new Error(\"Unreachable\");\n",
    "}\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup\n",
    "\n",
    "First we need to install the packages required\n",
    "\n",
    "```bash\n",
    "npm install @langchain/langgraph @langchain/anthropic @langchain/core\n",
    "```\n",
    "\n",
    "Next, we need to set API keys for Anthropic (the LLM we will use)\n",
    "\n",
    "```bash\n",
    "export ANTHROPIC_API_KEY=your-api-key\n",
    "```\n",
    "\n",
    "Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability.\n",
    "\n",
    "```bash\n",
    "export LANGCHAIN_TRACING_V2=\"true\"\n",
    "export LANGCHAIN_CALLBACKS_BACKGROUND=\"true\"\n",
    "export LANGCHAIN_API_KEY=your-api-key\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simple Usage\n",
    "\n",
    "Let's set up a very simple graph that facilitates this.\n",
    "First, we will have an LLM call that decides what action to take.\n",
    "Then we go to a human node. This node actually doesn't do anything - the idea is that we interrupt before this node and then apply any updates to the state.\n",
    "After that, we check the state and either route back to the LLM or to the correct tool.\n",
    "\n",
    "Let's see this in action!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "import {\n",
    "  MessagesAnnotation,\n",
    "  StateGraph,\n",
    "  START,\n",
    "  END,\n",
    "  MemorySaver,\n",
    "  Command,\n",
    "  interrupt\n",
    "} from \"@langchain/langgraph\";\n",
    "import { ChatAnthropic } from \"@langchain/anthropic\";\n",
    "import { tool } from '@langchain/core/tools';\n",
    "import { z } from 'zod';\n",
    "import { AIMessage, ToolMessage } from '@langchain/core/messages';\n",
    "import { ToolCall } from '@langchain/core/messages/tool';\n",
    "\n",
    "const weatherSearch = tool((input: { city: string }) => {\n",
    "    console.log(\"----\");\n",
    "    console.log(`Searching for: ${input.city}`);\n",
    "    console.log(\"----\");\n",
    "    return \"Sunny!\";\n",
    "}, {\n",
    "    name: 'weather_search',\n",
    "    description: 'Search for the weather',\n",
    "    schema: z.object({\n",
    "        city: z.string()\n",
    "    })\n",
    "});\n",
    "\n",
    "const model = new ChatAnthropic({ \n",
    "    model: \"claude-3-5-sonnet-latest\"\n",
    "}).bindTools([weatherSearch]);\n",
    "\n",
    "const callLLM = async (state: typeof MessagesAnnotation.State) => {\n",
    "    const response = await model.invoke(state.messages);\n",
    "    return { messages: [response] };\n",
    "};\n",
    "\n",
    "const humanReviewNode = async (state: typeof MessagesAnnotation.State): Promise<Command> => {\n",
    "    const lastMessage = state.messages[state.messages.length - 1] as AIMessage;\n",
    "    const toolCall = lastMessage.tool_calls![lastMessage.tool_calls!.length - 1];\n",
    "\n",
    "    const humanReview = interrupt<\n",
    "      {\n",
    "        question: string;\n",
    "        toolCall: ToolCall;\n",
    "      },\n",
    "      {\n",
    "        action: string;\n",
    "        data: any;\n",
    "      }>({\n",
    "        question: \"Is this correct?\",\n",
    "        toolCall: toolCall\n",
    "      });\n",
    "\n",
    "    const reviewAction = humanReview.action;\n",
    "    const reviewData = humanReview.data;\n",
    "\n",
    "    if (reviewAction === \"continue\") {\n",
    "        return new Command({ goto: \"run_tool\" });\n",
    "    }\n",
    "    else if (reviewAction === \"update\") {\n",
    "        const updatedMessage = {\n",
    "            role: \"ai\",\n",
    "            content: lastMessage.content,\n",
    "            tool_calls: [{\n",
    "                id: toolCall.id,\n",
    "                name: toolCall.name,\n",
    "                args: reviewData\n",
    "            }],\n",
    "            id: lastMessage.id\n",
    "        };\n",
    "        return new Command({ goto: \"run_tool\", update: { messages: [updatedMessage] } });\n",
    "    }\n",
    "    else if (reviewAction === \"feedback\") {\n",
    "        const toolMessage = new ToolMessage({\n",
    "          name: toolCall.name,\n",
    "          content: reviewData,\n",
    "          tool_call_id: toolCall.id\n",
    "        })\n",
    "        return new Command({ goto: \"call_llm\", update: { messages: [toolMessage] }});\n",
    "    }\n",
    "    throw new Error(\"Invalid review action\");\n",
    "};\n",
    "\n",
    "const runTool = async (state: typeof MessagesAnnotation.State) => {\n",
    "    const newMessages: ToolMessage[] = [];\n",
    "    const tools = { weather_search: weatherSearch };\n",
    "    const lastMessage = state.messages[state.messages.length - 1] as AIMessage;\n",
    "    const toolCalls = lastMessage.tool_calls!;\n",
    "\n",
    "    for (const toolCall of toolCalls) {\n",
    "        const tool = tools[toolCall.name as keyof typeof tools];\n",
    "        const result = await tool.invoke(toolCall.args);\n",
    "        newMessages.push(new ToolMessage({\n",
    "            name: toolCall.name,\n",
    "            content: result,\n",
    "            tool_call_id: toolCall.id\n",
    "        }));\n",
    "    }\n",
    "    return { messages: newMessages };\n",
    "};\n",
    "\n",
    "const routeAfterLLM = (state: typeof MessagesAnnotation.State): typeof END | \"human_review_node\" => {\n",
    "    const lastMessage = state.messages[state.messages.length - 1] as AIMessage;\n",
    "    if (!lastMessage.tool_calls?.length) {\n",
    "        return END;\n",
    "    }\n",
    "    return \"human_review_node\";\n",
    "};\n",
    "\n",
    "const workflow = new StateGraph(MessagesAnnotation)\n",
    "    .addNode(\"call_llm\", callLLM)\n",
    "    .addNode(\"run_tool\", runTool)\n",
    "    .addNode(\"human_review_node\", humanReviewNode, {\n",
    "      ends: [\"run_tool\", \"call_llm\"]\n",
    "    })\n",
    "    .addEdge(START, \"call_llm\")\n",
    "    .addConditionalEdges(\n",
    "        \"call_llm\",\n",
    "        routeAfterLLM,\n",
    "        [\"human_review_node\", END]\n",
    "    )\n",
    "    .addEdge(\"run_tool\", \"call_llm\");\n",
    "\n",
    "const memory = new MemorySaver();\n",
    "\n",
    "const graph = workflow.compile({ checkpointer: memory });"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import * as tslab from \"tslab\";\n",
    "\n",
    "const drawableGraph = graph.getGraph();\n",
    "const image = await drawableGraph.drawMermaidPng();\n",
    "const arrayBuffer = await image.arrayBuffer();\n",
    "\n",
    "await tslab.display.png(new Uint8Array(arrayBuffer));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example with no review\n",
    "\n",
    "Let's look at an example when no review is required (because no tools are called)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================ human Message (1) =================================\n",
      "hi!\n",
      "================================ ai Message (1) =================================\n",
      "Hello! I'm here to help you. I can assist you with checking the weather for different cities. Would you like to know the weather for a specific location? Just let me know which city you're interested in and I'll look that up for you.\n"
     ]
    }
   ],
   "source": [
    "let inputs = { messages: [{ role: \"user\", content: \"hi!\" }] };\n",
    "let config = { configurable: { thread_id: \"1\" }, streamMode: \"values\" as const };\n",
    "\n",
    "let stream = await graph.stream(inputs, config);\n",
    "\n",
    "for await (const event of stream) {\n",
    "    const recentMsg = event.messages[event.messages.length - 1];\n",
    "    console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
    "    console.log(recentMsg.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we check the state, we can see that it is finished, since there are no next steps to take:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[]\n"
     ]
    }
   ],
   "source": [
    "let state = await graph.getState(config);\n",
    "console.log(state.next);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example of approving tool\n",
    "\n",
    "Let's now look at what it looks like to approve a tool call"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================ human Message (1) =================================\n",
      "what's the weather in SF?\n",
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: 'Let me check the weather in San Francisco for you.'\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_01PTn9oqTP6EdFabfhfvELuy',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'San Francisco' }\n",
      "  }\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "inputs = { messages: [{ role: \"user\", content: \"what's the weather in SF?\" }] };\n",
    "config = { configurable: { thread_id: \"2\" }, streamMode: \"values\" as const };\n",
    "\n",
    "stream = await graph.stream(inputs, config);\n",
    "\n",
    "for await (const event of stream) {\n",
    "    const recentMsg = event.messages[event.messages.length - 1];\n",
    "    console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
    "    console.log(recentMsg.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we now check, we can see that it is waiting on human review"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 'human_review_node' ]\n"
     ]
    }
   ],
   "source": [
    "state = await graph.getState(config);\n",
    "console.log(state.next);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To approve the tool call, we can just continue the thread with no edits. To do so, we need to let `human_review_node` know what value to use for the `human_review` variable we defined inside the node. We can provide this value by invoking the graph with a `new Command({ resume: <human_review> })` input.  Since we're approving the tool call, we'll provide `resume` value of `{ action: \"continue\" }` to navigate to `run_tool` node:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: 'Let me check the weather in San Francisco for you.'\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_01PTn9oqTP6EdFabfhfvELuy',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'San Francisco' }\n",
      "  }\n",
      "]\n",
      "----\n",
      "Searching for: San Francisco\n",
      "----\n",
      "================================ tool Message (1) =================================\n",
      "Sunny!\n",
      "================================ ai Message (1) =================================\n",
      "It's sunny in San Francisco right now!\n"
     ]
    }
   ],
   "source": [
    "import { Command } from \"@langchain/langgraph\";\n",
    "\n",
    "for await (const event of await graph.stream(\n",
    "  new Command({ resume: { action: \"continue\" } }),\n",
    "  config\n",
    ")) {\n",
    "  const recentMsg = event.messages[event.messages.length - 1];\n",
    "  console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
    "  console.log(recentMsg.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Edit Tool Call\n",
    "\n",
    "Let's now say we want to edit the tool call. E.g. change some of the parameters (or even the tool called!) but then execute that tool."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================ human Message (1) =================================\n",
      "what's the weather in SF?\n",
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: 'Let me check the weather in San Francisco for you.'\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_01T7ykQ45XyGpzRB7MkPtSAE',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'San Francisco' }\n",
      "  }\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "inputs = { messages: [{ role: \"user\", content: \"what's the weather in SF?\" }] };\n",
    "config = { configurable: { thread_id: \"3\" }, streamMode: \"values\" as const };\n",
    "\n",
    "stream = await graph.stream(inputs, config);\n",
    "\n",
    "for await (const event of stream) {\n",
    "    const recentMsg = event.messages[event.messages.length - 1];\n",
    "    console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
    "    console.log(recentMsg.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we now check, we can see that it is waiting on human review"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 'human_review_node' ]\n"
     ]
    }
   ],
   "source": [
    "state = await graph.getState(config);\n",
    "console.log(state.next);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To edit the tool call, we will use `Command` with a different resume value of `{ action: \"update\", data: <tool call args> }`. This will do the following:\n",
    "\n",
    "* combine existing tool call with user-provided tool call arguments and update the existing AI message with the new tool call\n",
    "* navigate to `run_tool` node with the updated AI message and continue execution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: 'Let me check the weather in San Francisco for you.'\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_01T7ykQ45XyGpzRB7MkPtSAE',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'San Francisco' }\n",
      "  }\n",
      "]\n",
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: 'Let me check the weather in San Francisco for you.'\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_01T7ykQ45XyGpzRB7MkPtSAE',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'San Francisco' }\n",
      "  }\n",
      "]\n",
      "----\n",
      "Searching for: San Francisco\n",
      "----\n",
      "================================ tool Message (1) =================================\n",
      "Sunny!\n",
      "================================ ai Message (1) =================================\n",
      "It's sunny in San Francisco right now!\n"
     ]
    }
   ],
   "source": [
    "for await (const event of await graph.stream(\n",
    "  new Command({\n",
    "    resume: {\n",
    "      action: \"update\",\n",
    "      data: { city: \"San Francisco\" }\n",
    "    }\n",
    "  }),\n",
    "  config\n",
    ")) {\n",
    "  const recentMsg = event.messages[event.messages.length - 1];\n",
    "  console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
    "  console.log(recentMsg.content);\n",
    "}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Give feedback to a tool call\n",
    "\n",
    "Sometimes, you may not want to execute a tool call, but you also may not want to ask the user to manually modify the tool call. In that case it may be better to get natural language feedback from the user. You can then insert these feedback as a mock **RESULT** of the tool call.\n",
    "\n",
    "There are multiple ways to do this:\n",
    "\n",
    "1. You could add a new message to the state (representing the \"result\" of a tool call)\n",
    "2. You could add TWO new messages to the state - one representing an \"error\" from the tool call, other HumanMessage representing the feedback\n",
    "\n",
    "Both are similar in that they involve adding messages to the state. The main difference lies in the logic AFTER the `human_node` and how it handles different types of messages.\n",
    "\n",
    "For this example we will just add a single tool call representing the feedback. Let's see this in action!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================ human Message (1) =================================\n",
      "what's the weather in SF?\n",
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: \"I'll help you check the weather in San Francisco.\"\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_014cwxD65wDwQdNg6xqsticF',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'SF' }\n",
      "  }\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "inputs = { messages: [{ role: \"user\", content: \"what's the weather in SF?\" }] };\n",
    "config = { configurable: { thread_id: \"4\" }, streamMode: \"values\" as const };\n",
    "\n",
    "stream = await graph.stream(inputs, config);\n",
    "\n",
    "for await (const event of stream) {\n",
    "    const recentMsg = event.messages[event.messages.length - 1];\n",
    "    console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
    "    console.log(recentMsg.content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we now check, we can see that it is waiting on human review"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 'human_review_node' ]\n"
     ]
    }
   ],
   "source": [
    "state = await graph.getState(config);\n",
    "console.log(state.next);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To give feedback about the tool call, we will use `Command` with a different resume value of `{ action: \"feedback\", data: <feedback string> }`. This will do the following:\n",
    "\n",
    "* create a new tool message that combines existing tool call from LLM with the with user-provided feedback as content\n",
    "* navigate to `call_llm` node with the updated tool message and continue execution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: \"I'll help you check the weather in San Francisco.\"\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_014cwxD65wDwQdNg6xqsticF',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'SF' }\n",
      "  }\n",
      "]\n",
      "================================ tool Message (1) =================================\n",
      "User requested changes: use <city, country> format for location\n",
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: 'I apologize for the error. Let me search again with the proper format.'\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_01Jnm7sSZsiwv65YM4KsvfXk',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'San Francisco, USA' }\n",
      "  }\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "for await (const event of await graph.stream(\n",
    "  new Command({\n",
    "    resume: {\n",
    "      action: \"feedback\",\n",
    "      data: \"User requested changes: use <city, country> format for location\"\n",
    "    }\n",
    "  }),\n",
    "  config\n",
    ")) {\n",
    "  const recentMsg = event.messages[event.messages.length - 1];\n",
    "  console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
    "  console.log(recentMsg.content);\n",
    "}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that we now get to another breakpoint - because it went back to the model and got an entirely new prediction of what to call. Let's now approve this one and continue."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 'human_review_node' ]\n"
     ]
    }
   ],
   "source": [
    "state = await graph.getState(config);\n",
    "console.log(state.next);\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================ ai Message (1) =================================\n",
      "[\n",
      "  {\n",
      "    type: 'text',\n",
      "    text: 'I apologize for the error. Let me search again with the proper format.'\n",
      "  },\n",
      "  {\n",
      "    type: 'tool_use',\n",
      "    id: 'toolu_01Jnm7sSZsiwv65YM4KsvfXk',\n",
      "    name: 'weather_search',\n",
      "    input: { city: 'San Francisco, USA' }\n",
      "  }\n",
      "]\n",
      "----\n",
      "Searching for: San Francisco, USA\n",
      "----\n",
      "================================ tool Message (1) =================================\n",
      "Sunny!\n",
      "================================ ai Message (1) =================================\n",
      "The weather in San Francisco is currently sunny!\n"
     ]
    }
   ],
   "source": [
    "for await (const event of await graph.stream(\n",
    "  new Command({\n",
    "    resume: {\n",
    "      action: \"continue\",\n",
    "    }\n",
    "  }),\n",
    "  config\n",
    ")) {\n",
    "    const recentMsg = event.messages[event.messages.length - 1];\n",
    "    console.log(`================================ ${recentMsg._getType()} Message (1) =================================`)\n",
    "    console.log(recentMsg.content);\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "TypeScript",
   "language": "typescript",
   "name": "tslab"
  },
  "language_info": {
   "codemirror_mode": {
    "mode": "typescript",
    "name": "javascript",
    "typescript": true
   },
   "file_extension": ".ts",
   "mimetype": "text/typescript",
   "name": "typescript",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
